package hn.sigit.view.workflow.transaction;

import hn.sigit.logic.geometry.GeometryOperations;
import hn.sigit.logic.geometry.SplitException;
import hn.sigit.logic.geometry.SplitPolygonResult;
import hn.sigit.model.hnd.ladmshadow.Parcel;
import hn.sigit.model.hnd.ladmshadow.Property;
import hn.sigit.dao.hnd.ladmshadow.ParcelDAO;
import hn.sigit.dao.hnd.ladmshadow.PropertyDAO;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.hibernatespatial.mgeom.MCoordinate;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTWriter;


public class DataEntrySplitHelper implements Serializable {
	private static final long serialVersionUID = 1L;

	private DataEntryHelper dataEntryHelper;
	
	private List<Coord> currentSplitBorder;
	private double utmNorthing;
	private double utmEasting;

	
	public DataEntrySplitHelper(DataEntryHelper dataEntryHelper) {
		this.dataEntryHelper = dataEntryHelper;
	}

	public double getUtmNorthing() {
		return utmNorthing;
	}
	public void setUtmNorthing(double utmNorthing) {
		this.utmNorthing = utmNorthing;
	}

	public double getUtmEasting() {
		return utmEasting;
	}
	public void setUtmEasting(double utmEasting) {
		this.utmEasting = utmEasting;
	}
	
	public List<Coord> getCurrentSplitBorder() {
		if (currentSplitBorder == null)
			currentSplitBorder = new ArrayList<Coord>();
		return currentSplitBorder;
	}
	public void setCurrentSplitBorder(List<Coord> currentSplitBorder) {
		this.currentSplitBorder = currentSplitBorder;
	}

	public String addPointToBoundary() {
		Coord coord = new Coord(utmEasting, utmNorthing);
		getCurrentSplitBorder().add(coord);
		return null;
	}
	public String deleteLastBoundaryPoint() {
		List<Coord> coordList = getCurrentSplitBorder();
		if (coordList.size() > 0)
			coordList.remove(coordList.size() - 1);
		return null;
	}
	public String clearBoundary() {
		List<Coord> coordList = getCurrentSplitBorder();
		coordList.clear();
		return null;
	}
	
	
	public String applySplit() {
		//Creates the split border as a linestring object from the input coordinates
		Date timeStamp = new Date();
		
		int borderSize = currentSplitBorder.size();
		
		if (borderSize < 2) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							dataEntryHelper.getResBundle().loadMessage("dataentry.split.no_split_possible") + ": ",
							dataEntryHelper.getResBundle().loadMessage("dataentry.split.no_boundary")));
			return null;
		}
		
		Coord c;
		Coordinate[] coordinates = new MCoordinate[borderSize];
		for (int i = 0; i < coordinates.length; i++) {
			c = currentSplitBorder.get(i);
			coordinates[i] = new MCoordinate(c.getX(), c.getY());
		}
		
		Parcel parcelToSplit = (Parcel) dataEntryHelper.getSelectedParcel(); //ParcelDAO.loadParcelById(parcelToSplitId);
		
		if (parcelToSplit == null) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							dataEntryHelper.getResBundle().loadMessage("dataentry.split.no_split_possible") + ": ",
							dataEntryHelper.getResBundle().loadMessage("dataentry.split.no_parcel_to_split")));
			return null;
		}

		//proceed to split the parcel geometry object
		Polygon polygonToSplit = parcelToSplit.getShape();
		try {
			SplitPolygonResult spr = GeometryOperations.splitPolygon(polygonToSplit, coordinates);

			//TODO: Version 2: Cambiar a HashMap mapeando de llave a parcela
			List<Parcel> effectiveParcelNeighbors = ParcelDAO.loadNeighborsByParcel(parcelToSplit);
			Map<Long,Parcel> correctedNeighbors = GeometryOperations.eliminateGaps(parcelToSplit, effectiveParcelNeighbors, spr);
			
			/*
			 * NOTE: This commented code is if we want to create a new parcel object version
			 * if we just changed the geometry of this neighbor parcel
			 * 
			for (Parcel oldParcel : effectiveParcelNeighbors) {
				Parcel newParcel = correctedNeighbors.get(oldParcel.getSuID());
				if (newParcel != null) {
					Util.replaceParcel(oldParcel, newParcel, timeStamp, false);
				}
			}
			*/

			/*NOTE: This code only modifies the geometry object without creating
			 * a new version of it
			 */
			for (Parcel neighborParcel : effectiveParcelNeighbors) {
				Parcel correctedParcel = correctedNeighbors.get(neighborParcel.getSuID());
				//since not all neighbors have to be corrected, we have to check for null here
				if (correctedParcel != null) {
					neighborParcel.setShape(correctedParcel.getShape());
					neighborParcel.setModified(true);
					neighborParcel.setOriginal(false);
					ParcelDAO.save(neighborParcel);
				}
			}
			
			
			Property originalProperty = parcelToSplit.getProperty();
			
			
			Property newProperty1 = originalProperty.clone();
			newProperty1.setBeginLifespanVersion(timeStamp);
			
			Parcel newParcel1 = parcelToSplit.clone();
			newParcel1.setBeginLifespanVersion(timeStamp);
			newParcel1.setShape(spr.getSplitPolygon1());
			newParcel1.getBaunits().clear();
			newParcel1.getBaunits().add(newProperty1);
			//TODO: ver lo de la ficha
			newParcel1.setFieldTab(dataEntryHelper.getFieldTabCounter());
			
			newProperty1.getSpatialUnits().clear();
			newProperty1.getSpatialUnits().add(newParcel1);
			
			ParcelDAO.save(newParcel1);
			PropertyDAO.save(newProperty1);

			
			
			Property newProperty2 = originalProperty.clone();
			newProperty2.setBeginLifespanVersion(timeStamp);
			
			Parcel newParcel2 = parcelToSplit.clone();
			newParcel2.setBeginLifespanVersion(timeStamp);
			newParcel2.setShape(spr.getSplitPolygon2());
			newParcel2.getBaunits().clear();
			newParcel2.getBaunits().add(newProperty2);
			//TODO: ver lo de la ficha
			newParcel2.setFieldTab(dataEntryHelper.getFieldTabCounter());
			
			newProperty2.getSpatialUnits().clear();
			newProperty2.getSpatialUnits().add(newParcel2);

			ParcelDAO.save(newParcel2);
			PropertyDAO.save(newProperty2);

			//mark as modified
			parcelToSplit.setModified(true);
			parcelToSplit.setEndLifespanVersion(timeStamp);
			originalProperty.setEndLifespanVersion(timeStamp);
			
			 
			ParcelDAO.save(parcelToSplit);
			PropertyDAO.save(originalProperty);

			this.clearBoundary();
			
			dataEntryHelper.setSelectedParcel(newParcel1);
		}
		catch (SplitException se) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							dataEntryHelper.getResBundle().loadMessage(se.getExceptionBundle()) + ": ",
							dataEntryHelper.getResBundle().loadMessage(se.getMessage())));
			se.printStackTrace();
		}
		
		return null;
	}

	public String getSplitBorderWKT() {
		int borderSize = currentSplitBorder.size();
		
		if (borderSize > 1) {
			WKTWriter wktWriter = new WKTWriter();
			Coord c;
			Coordinate[] coordinates = new Coordinate[borderSize];
			for (int i = 0; i < coordinates.length; i++) {
				c = currentSplitBorder.get(i);
				coordinates[i] = new Coordinate(c.getX(), c.getY());
			}

			LineString ls = GeometryOperations.geomFactory.createLineString(coordinates);

			return wktWriter.write(ls);
		}

		return "";
	}


	public class Coord {
		private double x;
		private double y;
		
		public Coord(double x, double y) {
			this.x = x;
			this.y = y;
		}
		
		public double getX() {
			return x;
		}
		public void setX(double x) {
			this.x = x;
		}
		
		public double getY() {
			return y;
		}
		public void setY(double y) {
			this.y = y;
		}
	}
}
